package ex3.executor;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import ex3.Constants.State;
import ex3.matchmaker.Matchmaker;

@SuppressWarnings("serial")
public class ExecutorImpl extends UnicastRemoteObject implements Executor {

	public Random random = new Random();
	private boolean selfInitiatedStateChange = false;
	private List<Character> availableActions = new ArrayList<Character>();
	private int myID;
	State state;
	private Matchmaker matchmaker = null;
	private String logFile = "Executor";
	public final static String ERROR_MESSAGE = "EXECUTOR_ERROR";

	public ExecutorImpl(String regHost, int regPort) throws Exception {
		try {
			matchmaker = (Matchmaker) Naming.lookup("//" + regHost + ":"
					+ regPort + "/matchmaker");
		} catch (Exception e) {
			System.out.println("Executor failed to find matchmaker: "
					+ e.getMessage());
			System.out.println("Exiting.");
			e.printStackTrace();
			System.exit(1);
		}
		try {
			myID = matchmaker.assignExecutorID();
		} catch (Exception e) {
			System.out.println("Executor failed receive ID: " + e.getMessage());
			System.out.println("Exiting.");
			e.printStackTrace();
			System.exit(1);
		}
		logFile = logFile + myID;
		try {
			new FileWriter(logFile, false);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			Naming.rebind("//" + regHost + ":" + regPort + "/Executor" + myID,
					this);
		} catch (Exception e) {
			Naming.bind("//" + regHost + ":" + regPort + "/Executor" + myID,
					this);
		}
		state = State.IDLE;
	}

	private void registerWithMatchmaker() {
		try {
			matchmaker.updateExecutor("Executor" + myID, state,
					availableActions);
		} catch (Exception e) {
			System.out.println("Executor failed to register with matchmaker: "
					+ e.getMessage());
			System.out.println("Exiting.");
			e.printStackTrace();
			System.exit(1);
		}
	}

	private void log(String result) {
		try {
			BufferedWriter fw = new BufferedWriter(
					new FileWriter(logFile, true));
			fw
					.write(new SimpleDateFormat().format(new Date()) + " - "
							+ result);
			fw.newLine();
			fw.close();
		} catch (IOException e) {
		}
	}

	private synchronized void switchState(State newState)
			throws RemoteException {
		if (state == State.BUSY && newState == state) {
			if (!selfInitiatedStateChange) {
				throw new RuntimeException("Correctness error: attempting "
						+ "to invoke task on busy executor");
			} else {
				throw new RemoteException("Executor is busy");
			}
		}
		state = newState;
		try {
			if (state == State.IDLE) {
				matchmaker.updateExecutor("Executor" + myID, state,
						availableActions);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			System.err.println("Failed to update matchmaker, exiting");
			System.exit(1);
		}
	}

	private void addOperation(char op) {
		availableActions.add(op);
	}

	@SuppressWarnings("unused")
	private void removeOperation(char op) {
		availableActions.remove(op);
	}

	/**
	 * This is the main function called by remote entities
	 * 
	 * @throws RemoteException
	 */
	public String execute(char op, int a, int b) throws RemoteException {
		switchState(State.BUSY);
		double rand = (random.nextDouble() * 10);
		if (rand > 9) { // 10% of failure
			switchState(State.IDLE);
			log("\"Failed\" to execute: " + op + ":" + a + ":" + b);
			return "EXECUTOR_ERROR";
		}
		try {
			if (!availableActions.contains(op)) {
				throw new RuntimeException("Correctness error: "
						+ "attempting to invoke unsupported operation");
			}
			log(op + ":" + a + ":" + b);
			return Integer.valueOf(new Integer(Operation.doOp(op, a, b)))
					.toString();
		} catch (IllegalArgumentException ex) {
			throw new RemoteException("This action " + op + " is not supported");
		} finally {
			rand = (random.nextDouble() * 10);
			if (rand > 9) { // 10% of changing operations
				// Change operations list
				availableActions.clear();
				List<Character> x = new LinkedList<Character>();
				x.add('+');
				x.add('-');
				x.add('/');
				x.add('a');
				x.add('b');
				x.add('c');
				x.add('d');
				x.add('e');
				x.add('f');
				for (Character ch : x) {
					if (random.nextBoolean() == true) {
						availableActions.add(ch);
					}
				}
				log("Changing operations list to: "
						+ availableActions.toString());
			}
			switchState(State.IDLE);
		}
	}

	public static void main(String[] args) throws Exception {
		if (args.length < 2) {
			System.err
					.println("Executor requires host address and port number.");
			System.err.println("Exiting.");
			System.exit(1);
		}
		int port = (new Integer(args[1])).intValue();
		ExecutorImpl e = new ExecutorImpl(args[0], port);
		for (int i = 2; i < args.length; i++) {
			e.addOperation(args[i].charAt(0));
		}
		e.registerWithMatchmaker();
	}
}
